﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Net.NetworkInformation;
using System.Net;
using System.Net.Sockets;
using System.Collections.ObjectModel;

namespace OnkyoController
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : IOnkyoCommandListener
    {
        #region MEMBERS

        private readonly MainController _controller;
        private readonly GrowlNotifications _growlNotifications = new GrowlNotifications();

        private readonly BackgroundWorker _serverSimulatorBackgroundWorker;
        private readonly BackgroundWorker _serverSearcherBackgroundWorker;

        #endregion

        #region PROPERTIES

        public ObservableCollection<string> Receivers { get; set; }

        public bool Connected
        {
            get { return (bool)GetValue(ConnectedProperty); }
            set { SetValue(ConnectedProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Connected.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ConnectedProperty =
            DependencyProperty.Register("Connected", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));

        #endregion

        /// <summary>
        /// Setup everything necessary for the application.
        /// </summary>
        public MainWindow()
        {
            Receivers = new ObservableCollection<string>();

            _serverSearcherBackgroundWorker = new BackgroundWorker();
            _serverSearcherBackgroundWorker.WorkerSupportsCancellation = true;
            _serverSearcherBackgroundWorker.DoWork += SearchForReceivers;

            InitializeComponent();

            _growlNotifications.Top = SystemParameters.WorkArea.Top + 20;
            _growlNotifications.Left = SystemParameters.WorkArea.Left + SystemParameters.WorkArea.Width - 380;

            foreach (var commandLineArgument in Environment.GetCommandLineArgs())
            {
                switch (commandLineArgument)
                {
                    case "/debug":
                        // Start a debug receiver simulator if we don't have a real receiver
                        // to test with. To do this, send /debug as a command line argument.
                        _serverSimulatorBackgroundWorker = new BackgroundWorker();
                        _serverSimulatorBackgroundWorker.WorkerSupportsCancellation = true;
                        _serverSimulatorBackgroundWorker.DoWork += (o, args) =>
                        {
                            using (new ReceiverSimulator.ReceiverSimulator())
                            {
                            }
                        };
                        _serverSimulatorBackgroundWorker.RunWorkerAsync();
                        break;
                }
            }

            //Get a reference to the controller that you use to send commands to the
            //receiver with.
            _controller = MainController.GetInstance();
            _controller.AddCommandListener(this);
        }

        #region CONNECTION_HELPERS

        /// <summary>
        /// Searches the network for available receivers.
        /// </summary>
        private void SearchForReceivers(object sender, DoWorkEventArgs e)
        {
            // Get the hostname
            var host = Dns.GetHostName();

            var ipsInNetwork = Dns.GetHostEntry(host);

            var ip = ipsInNetwork.AddressList.First(a => a.AddressFamily == AddressFamily.InterNetwork);
            var ipBase = ip.ToString().Substring(0, ip.ToString().LastIndexOf('.') + 1);

            for (var i = 1; i < 255; i++)
            {
                IPAddress testIp;
                if (!IPAddress.TryParse(ipBase + i, out testIp))
                {
                    continue;
                }

                var p = new Ping();
                p.PingCompleted += p_PingCompleted;
                p.SendAsync(testIp, 100, testIp);
            }
        }

        private void p_PingCompleted(object sender, PingCompletedEventArgs e)
        {
            if (e.Reply.Status == IPStatus.Success)
            {
                TestConnection(e.Reply.Address);
            }
        }

        /// <summary>
        /// Helper method to SearchForReceivers that tests 
        /// the connection to a specific IP address.
        /// </summary>
        /// <param name="testIp"></param>
        private void TestConnection(IPAddress testIp)
        {
            var receiverPort = int.Parse(OnkyoController.Resources.GUITexts.DEFAULT_PORT);
            var testSocket = new TcpClient();
            try
            {
                Console.WriteLine("Trying " + testIp);

                testSocket.Connect(testIp, receiverPort);
                if (testSocket.Connected)
                {
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        if (!Receivers.Contains(testIp.ToString()))
                        {
                            Receivers.Add(testIp.ToString());
                        }
                    }));
                }
            }
            catch (SocketException e)
            {
                Console.WriteLine(e.Message);
            }
            finally
            {
                testSocket.Close();

                if (Receivers.Count == 0)
                {
                    SearchForReceivers(null, null);
                }
            }
        }

        #endregion

        #region CLICK_HANDLERS

        /// <summary>
        /// Called when you press one of the "Input" buttons.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnInputClick(object sender, RoutedEventArgs e)
        {
            var button = sender as Button;

            if (_controller == null || !_controller.Connected || button == null)
            {
                return;
            }

            if (button.Tag != null)
            {
                //Get the button tag and parse it as a string. This string
                //is the corresponding command that you send to the receiver.
                _controller.ChangeInput(button.Tag as string);
            }

            foreach (var b in ButtonGrid.Children.OfType<Button>())
            {
                //Make the button you just pressed disabled and
                //the rest of them enabled.
                b.IsEnabled = !b.Equals(button);
            }
        }

        /// <summary>
        /// Called when you click the "Connect" button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnConnectClick(object sender, RoutedEventArgs e)
        {
            IPAddress hostname;

            //Parse the default port from the resource file.
            var port = int.Parse(OnkyoController.Resources.GUITexts.DEFAULT_PORT);

            //Check if the user has selected an IP address from the receivers combobox.
            if (ScannedServers.SelectedItem == null ||
                !IPAddress.TryParse(ScannedServers.SelectedItem.ToString(), out hostname))
            {
                return;
            }

            //Try to connect in a background thread so that we don't block the GUI.
            var bgWorker = new BackgroundWorker();
            bgWorker.DoWork += (o, args) =>
            {
                Dispatcher.BeginInvoke(new Action(() =>
                {
                    Mouse.OverrideCursor = Cursors.Wait;
                }));

                _controller.Connect(hostname, port);
            };

            bgWorker.RunWorkerCompleted += (o, args) =>
            {
                Dispatcher.BeginInvoke(new Action(() =>
                {
                    Mouse.OverrideCursor = null;
                }));

                if (_controller.Connected)
                {
                    Connected = true;

                    _controller.GetArtist();
                    _controller.GetVolumeLevel();
                    _controller.GetSongTitle();
                }
                else
                {
                    //If we did not manage to connect, we show an error message popup.
                    //We do this in Dispatcher.BeginInvoke because the Dispatcher
                    //thread owns the GUI objects.
                    Dispatcher.BeginInvoke(new Action(() =>
                        MessageBox.Show(OnkyoController.Resources.ErrorMessages.COULD_NOT_CONNECT,
                            OnkyoController.Resources.ErrorMessages.CONNECTION_ERROR_CAPTION,
                            MessageBoxButton.OK,
                            MessageBoxImage.Error)
                        ));
                }
            };

            //Don't forget to run the BackgroundWorker after we set everything up!
            bgWorker.RunWorkerAsync();
        }

        /// <summary>
        /// Called when you click the Disconnect button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDisconnectClick(object sender, RoutedEventArgs e)
        {
            var bgWorker = new BackgroundWorker();

            bgWorker.DoWork += (o, args) => _controller.Disconnect();

            bgWorker.RunWorkerCompleted += (o, args) =>
            {
                if (_controller.Connected)
                {
                    return;
                }

                Connected = false;
            };

            bgWorker.RunWorkerAsync();
        }

        /// <summary>
        /// Called when you drag the volume slider.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnVolumeChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            //Change the text on the volume label. The slider value
            //is between 0 and 64 because those are the values that
            //the receiver uses. Therefore, we convert it so that
            //the label shows values between 0 and 100.
            if (VolumeLabel != null)
            {
                VolumeLabel.Content = (int)(1.5625 * VolumeSlider.Value);
            }

            if (_controller != null && _controller.Connected)
            {
                //Send a command to the receiver to change the volume
                //to the new value.
                _controller.ChangeVolume(VolumeSlider.Value);
            }
        }

        /// <summary>
        /// Called when you click the "Refresh" button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnRefreshServers(object sender, RoutedEventArgs e)
        {
            Receivers.Clear();

            _serverSearcherBackgroundWorker.RunWorkerAsync();
        }

        /// <summary>
        /// Called when you click the "Play/Pause" button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PlayPauseButton_OnClick(object sender, RoutedEventArgs e)
        {
            if (PlayPauseButton.Content.Equals("Play"))
            {
                _controller.Play();
                PlayPauseButton.Content = "Pause";
            }
            else
            {
                _controller.Pause();
                PlayPauseButton.Content = "Play";
            }
        }

        /// <summary>
        /// Called when you click the "Previous" button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PreviousTrack_OnClick(object sender, RoutedEventArgs e)
        {
            if (_controller == null)
            {
                return;
            }

            _controller.PreviousTrack();
        }

        /// <summary>
        /// Called when you click the "Next" button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NextTrack_OnClick(object sender, RoutedEventArgs e)
        {
            if (_controller == null)
            {
                return;
            }

            _controller.NextTrack();
        }

        /// <summary>
        /// Called when closing the main window.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainWindow_OnClosed(object sender, EventArgs e)
        {
            if (_serverSimulatorBackgroundWorker != null && _serverSimulatorBackgroundWorker.IsBusy)
            {
                _serverSimulatorBackgroundWorker.Dispose();
            }

            _serverSearcherBackgroundWorker.Dispose();

            Environment.Exit(0);
        }

        #endregion

        #region EVENT_LISTENERS

        public void OnVolumeLevelChanged(double newVolumeLevel)
        {
        }

        public void OnArtistChanged(string newArtist)
        {
            Dispatcher.BeginInvoke(new Action(() => ArtistLabel.Content = newArtist));
        }

        public void OnSongTitleChanged(string newSongTitle)
        {
            Dispatcher.BeginInvoke(new Action(() =>
            {
                SongTitleLabel.Content = newSongTitle;
                _growlNotifications.AddNotification(new Notification()
                    {
                        Title = "New Song",
                        Artist = ArtistLabel.Content.ToString(),
                        SongTitle = SongTitleLabel.Content.ToString()
                    }
                );
            }));
        }

        public void OnPlayTimeChanged(string newTime)
        {
            Dispatcher.BeginInvoke(new Action(() =>
            {
                PlayTimeLabel.Content = newTime;
                PlayPauseButton.Content = "Pause";
            }));
        }

        #endregion
    }
}
